Function.RegisterNamespace("Test");

[Import("../../Source/Extensions.js/Array.js")]
[Fixture]
Test.Array=function(){
    [Fixture]
    function Clear(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.Clear: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Clear(new Object());
            });

            Assert.Equal(expected,actual);        
        }
        [Fact]
        function ClearsArrayElements(){
            var expected=0;
            var target=[1,2,3];
            
            Array.Clear(target);
            var actual=target.length;
            
            Assert.Equal(expected,actual);
        }
    }
    
    [Fixture]
    function Contains(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.Contains: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Contains(new Object());
            });

            Assert.Equal(expected,actual);        
        }
        [Fact]
        function ReturnsTrueIfArrayContainsExpected(){
            var expected=3;
            var target=[1,2,expected,4,5];
            
            var actual=Array.Contains(target,expected);
            
            Assert.True(actual,"ArrayTest.Contains.ReturnsTrueIfArrayContainsExpected: Array.Contains returned false.");
        }
        [Fact]
        function ReturnsFalseIfArrayDoesNotContainExpected(){
            var expected=3;
            var target=[1,2,4,5];
            
            var actual=Array.Contains(target,expected);
            
            Assert.False(actual,"ArrayTest.Contains.ReturnsFalseIfArrayDoesNotContainExpected: Array.Contains returned true.");
        }
    }

    [Fixture]
    function Copy(){
        [Fact]
        function ThrowsIfPassedInvalidSourceArray(){
            var expected="Array.Copy: 'sourceArray' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Copy(new Object());
            });

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ThrowsIfPassedInvalidSourceIndex(){
            var expected="Array.Copy: 'sourceIndex' must be a valid Number.";
            
            var actual=Record.Exception(function(){
                Array.Copy([],{});
            });

            Assert.Equal(expected,actual);        
        }
        [Fact]
        function ThrowsIfPassedNegativeSourceIndex(){
            var expected="Array.Copy: 'sourceIndex' may not be negative.";

            var actual=Record.Exception(function(){
                Array.Copy([],-1);
            });

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ThrowsIfPassedInvalidDestinationArray(){
            var expected="Array.Copy: 'destinationArray' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Copy([],0,new Object());
            });

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ThrowsIfPassedInvalidDestinationIndex(){
            var expected="Array.Copy: 'destinationIndex' must be a valid Number.";
            
            var actual=Record.Exception(function(){
                Array.Copy([],0,[],{});
            });

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ThrowsIfPassedNegativeDestinationIndex(){
            var expected="Array.Copy: 'destinationIndex' may not be negative.";

            var actual=Record.Exception(function(){
                Array.Copy([],0,[],-1);
            });

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ThrowsIfPassedInvalidLength(){
            var expected="Array.Copy: 'length' must be a valid Number.";
            
            var actual=Record.Exception(function(){
                Array.Copy([],0,[],0,null);
            });

            Assert.Equal(expected,actual);
        }
        [Fact]
        function CopiesSourceArrayToDestinationArray(){
            var expected=[1,2,3,4,5];
            var target=expected;
            var actual=[];
            
            Array.Copy(target,0,actual,0,target.length);
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function SplicesSourceArrayIntoDestinationArray(){
            var expected=[1,2,3,4,5];
            var target=expected.slice(2,4);
            var actual=[1,2,5];
            
            Array.Copy(target,0,actual,2,target.length);
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function CopiesToEndOfSourceArrayIfLengthOmitted(){
            var expected=[1,2,3,4,5];
            var target=expected;
            var actual=[];
            
            Array.Copy(target,0,actual,0);
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function CopiesToEndOfSourceArrayIfLengthIsNegative(){
            var expected=[1,2,3,4,5];
            var target=expected;
            var actual=[];
            
            Array.Copy(target,0,actual,0,-1);
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function CreatesDuplicateOfSourceArray(){
            var expected=[1,2,3,4,5];
            
            var actual=Array.Copy(expected);

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ConvertsArgumentsCollectionToArray(){
            var expected=[1,2,3,4,5];
            var actual=null;
            var indirection=function(){
                actual=Array.Copy(arguments);
            }

            indirection.apply(Object.Global(),expected);

            Assert.Equal(expected,actual);
        }

    }
    [Fixture]
    function Equals(){
        [Fact]
        function ThrowsIfExpectedIsNotAValidArray(){
            var expected="Array.Equals: 'expected' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Equals(new Object());
            });
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function ReturnsFalseIfActualIsNotAValidArray(){
            var actual=Array.Equals([],null);
            
            Assert.False(actual);
        }
        [Fact]
        function ReturnsReasonIfActualIsNotAValidArray(){
            var expected="Array.Equals: 'actual' was not a valid Array.";
            var reason={};
            
            Array.Equals([],null,reason);
            var actual=reason.Value;

            Assert.Equal(expected,actual);
        }

        [Fact]
        function ReturnsFalseIfLengthsDontMatch(){            
            var actual=Array.Equals([1],[]);        
            
            Assert.False(actual);
        }

        [Fact]
        function ReturnsReasonIfLengthsDontMatch(){
            var expected="Array.Equals: Expected array of length '1', found array of length '0'.";
            var reason={};
            
            Array.Equals([1],[],reason);        
            var actual=reason.Value;

            Assert.Equal(expected,actual);
        }

        [Fact]
        function ReturnsFalseIfItemsDontMatch(){
            var actual=Array.Equals([1,2],[1,1]);
            
            Assert.False(actual);
        }

        [Fact]
        function ReturnsReasonIfItemsDontMatch(){
            var expected="Array.Equals: Unexpected value found at position [1]. Expected '2', found '1'."
            var reason={};
            
            Array.Equals([1,2],[1,1],reason);
            var actual=reason.Value;

            Assert.Equal(expected,actual);
        }
        [Fact]
        function ReturnsTrueIfAllItemsMatch(){
            var targetExpected=[1,2,3,4,5];
            var targetActual=[1,2,3,4,5];

            var actual=Array.Equals(targetExpected,targetActual);

            Assert.True(actual);
        }
    }
    
    [Fixture]
    function Find(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.Contains: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Contains(new Object());
            });
            
            Assert.Equal(expected,actual.toString());
        }
        [Fact]
        function ReturnsNullIfNoMatchFound(){
            var actual=Array.Find([],"");
        
            Assert.Null(actual);
        }
        [Fact]
        function ReturnsMatchIfFound(){
            var expected=3;
            
            var actual=Array.Find([1,2,expected,4,5],expected);
        
            Assert.Equal(expected,actual);
        }
        [Fact]
        function CallsComparerIfSpecified(){
            var expected=10;
            var actual=null;
            
            Array.Find([1,2,expected],100,function(a,b){
                actual=expected;
                return a<b;
            });

            Assert.Equal(expected,actual);
        }

    }
    
    [Fixture]
    function ForEach(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.ForEach: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.ForEach(new Object());
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfPassedInvalidHandler(){
            var expected="Array.ForEach: argument 'handler' must be a valid Function pointer.";

            var actual=Record.Exception(function(){
                Array.ForEach([],new Object());
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfPassedInvalidPredicate(){
            var expected="Array.ForEach: 'predicate' must be a valid Function pointer.";

            var actual=Record.Exception(function(){
                Array.ForEach([],function(){},new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function DoesNotThrowIfNotPassedPredicate(){
            var actual=Record.Exception(function(){
                Array.ForEach([],function(){});
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotThrowIfPassedNullPredicate(){
            var actual=Record.Exception(function(){
                Array.ForEach([],function(){},null);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotThrowIfPassedUndefinedPredicate(){
            var actual=Record.Exception(function(){
                Array.ForEach([],function(){},undefined);
            });
            
            Assert.Null(actual);
        }
        
        [Fact]
        function CallsPredicateForDecision(){
            var expected="predicate";
            var actual=null;
            
            Array.ForEach([1],function(){},function(){
                actual=expected;
            })
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function PassesItemToPredicate(){
            var expected=1;
            var actual=null;
            
            Array.ForEach([expected],function(){},function(item){
                actual=item;
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function PassesArrayContextToPredicate(){
            var target=[1];
            var expected={Cancel:false,Current:target[0],Index:target.length,Instance:target};
            var actual=null;
            
            Array.ForEach(target,function(){},function(item,context){
                actual=context;
            })
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function DoesNotProcessItemWhenPredicateReturnsFalse(){
            var actual=Record.Exception(function(){
                Array.ForEach([1],function(){
                    throw new Error("ArrayTest.ForEach.DoesNotProcessItemWhenPredicateReturnsFalse: Predicate failed to cancel processing.");
                },function(){
                    return false;
                })
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotProcessItemWhenPredicateReturnsUndefined(){
            var actual=Record.Exception(function(){
                Array.ForEach([1],function(){
                    throw new Error("ArrayTest.ForEach.DoesNotProcessItemWhenPredicateReturnsUndefined: Predicate failed to cancel processing.");
                },function(){})
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotProcessItemWhenPredicateReturnsNull(){
            var actual=Record.Exception(function(){
                Array.ForEach([1],function(){
                    throw new Error("ArrayTest.ForEach.DoesNotProcessItemWhenPredicateReturnsUndefined: Predicate failed to cancel processing.");
                },function(){
                    return null;
                })
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotProcessItemWhenPredicateReturnsZero(){
            var actual=Record.Exception(function(){
                Array.ForEach([1],function(){
                    throw new Error("ArrayTest.ForEach.DoesNotProcessItemWhenPredicateReturnsUndefined: Predicate failed to cancel processing.");
                },function(){
                    return 0;
                })
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotProcessItemWhenPredicateReturnsEmptyString(){
            var actual=Record.Exception(function(){
                Array.ForEach([1],function(){
                    throw new Error("ArrayTest.ForEach.DoesNotProcessItemWhenPredicateReturnsUndefined: Predicate failed to cancel processing.");
                },function(){
                    return '';
                })
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function ProcessesItemWhenPredicateReturnsTrue(){
            var expected="handler";
            var actual=null;
            
            Array.ForEach([1],function(){
                actual=expected;
            },function(){
                return true;
            })
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ProcessesItemWhenPredicateReturnsObject(){
            var expected="handler";
            var actual=null;
            
            Array.ForEach([1],function(){
                actual=expected;
            },function(){
                return new Object();
            })
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function ProcessesItemWhenPredicateReturnsNonZeroNumber(){
            var expected="handler";
            var actual=null;
            
            Array.ForEach([1],function(){
                actual=expected;
            },function(){
                return -1;
            })
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function ProcessesItemWhenPredicateReturnsNonEmptyString(){
            var expected="handler";
            var actual=null;
            
            Array.ForEach([1],function(){
                actual=expected;
            },function(){
                return expected;
            })
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function PassesItemToHandler(){
            var expected=1;
            var actual=null;
            
            Array.ForEach([expected],function(item){
                actual=item;
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function PassesArrayContextToHandler(){
            var target=[1];
            var expected={Cancel:false,Current:target[0],Index:target.length,Instance:target};
            var actual=null;
            
            Array.ForEach(target,function(item,context){
                actual=context;
            })

            Assert.Equal(expected,actual);
        }

        [Fact]
        function ProcessesAllItemsInArray(){
            var expected=[1,2,3,4,5];
            var actual=[];
            
            Array.ForEach(expected,function(item){
                actual.push(item);
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function LoopBreaksIfCancelIsSetOnContext(){
            var target=[1,2,3,4,5];
            var expected=target.slice(0,3);
            var actual=[];
            
            Array.ForEach(target,function(item,context){
                actual.push(item);
                if(actual.length==expected.length)context.Cancel=true;
            })

            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ReturnsArrayContextAfterLoop(){
            var target=[1];
            var expected={Cancel:false,Current:target[0],Index:target.length,Instance:target};

            var actual=Array.ForEach(target,function(){});
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function PassesContextThroughIfProvided(){
            var expected={CustomContext:new Object()};
            
            var actual=Array.ForEach([],function(){},null,expected);
            
            Assert.Same(expected,actual);
        }
        
    }
    
    [Fixture]
    function IndexOf(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.IndexOf: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.IndexOf(new Object());
            });
            
            Assert.Equal(expected,actual);
        }
        [Fact]
        function DoesNotThrowIfStartingIndexOmitted(){
            var actual=Record.Exception(function(){
                Array.IndexOf([],0);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotThrowIfStartingIndexUndefined(){
            var actual=Record.Exception(function(){
                Array.IndexOf([],0,undefined);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotThrowIfStartingIndexNull(){
            var actual=Record.Exception(function(){
                Array.IndexOf([],0,null);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function ThrowsIfStartingIndexIsNotAValidNumber(){
            var expected="Array.IndexOf: 'startingIndex' must be a valid Number.";

            var actual=Record.Exception(function(){
                Array.IndexOf([],0,new Object());
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function StartsAtStartingIndexIfSpecified(){
            var expected=3;
            var match=1;
            var target=[match,2,2];
            target[expected]=match;
            
            var actual=Array.IndexOf(target,match,1);
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function DoesNotThrowIfPredicateIsOmitted(){
            var actual=Record.Exception(function(){
                Array.IndexOf([],0,0);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotThrowIfPredicateIsNull(){
            var actual=Record.Exception(function(){
                Array.IndexOf([],0,0,null);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function DoesNotThrowIfPredicateIsUndefined(){
            var actual=Record.Exception(function(){
                Array.IndexOf([],0,0,undefined);
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function ThrowsIfPredicateIsNotAValidFunction(){
            var expected="Array.IndexOf: 'predicate' must be a valid Function pointer.";

            var actual=Record.Exception(function(){
                Array.IndexOf([],0,0,new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function PassesItemToPredicate(){
            var expected="predicate";
            var actual=null;

            Array.IndexOf([expected],expected,0,function(item){
                actual=item;
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function PassesContextToPredicate(){
            var target=[1];
            var expected={Cancel:false,Current:target[0],Expected:target[0],Index:target.length,Instance:target};
            var actual=null;

            Array.IndexOf(target,target[0],0,function(item,context){
                actual=context;
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function UsesCustomContextIfPassed(){
            var expected={CustomContext:"object"};
            var actual=null;

            Array.IndexOf([1],1,0,function(item,context){
                actual=context;
            },expected);

            Assert.Same(expected,actual);
        }
        
        [Fact]
        function UsesObjectEqualsIfPredicateOmitted(){
            var target=[1,2,3,4,5];
            var expected=2;

            var actual=Array.IndexOf(target,target[expected]);
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function LoopBreaksIfCancelIsSetOnContext(){
            var target=[1,2,3,4,5];
            var expected=target.slice(0,3);
            var actual=[];
            
            Array.IndexOf(target,0,0,function(item,context){
                actual.push(item);
                if(actual.length==expected.length)context.Cancel=true;
            })

            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ReturnsNegativeOneIfNoMatchFound(){
            var expected=-1;
            
            var actual=Array.IndexOf([],0);
        
            Assert.Equal(expected,actual);
        }
    }

    [Fixture]
    function IsEmpty(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.IsEmpty: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.IsEmpty(new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ReturnsFalseIfArrayHasLength(){
            var target=[];
            target.length=1;

            var actual=Array.IsEmpty(target);

            Assert.False(actual);
        }

        [Fact]
        function ReturnsFalseIfArrayHasEntry(){
            var target=[1];

            var actual=Array.IsEmpty(target);

            Assert.False(actual);
        }

        [Fact]
        function ReturnsTrueIfArrayIsEmpty(){
            var target=[];

            var actual=Array.IsEmpty(target);

            Assert.True(actual);
        }
    }
    
    [Fixture]
    function Remove(){
        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.Remove: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Remove(new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ReturnsNullIfNoMatchFound(){
            var target=[1,2,3];
            
            var actual=Array.Remove(target,4);
            
            Assert.Null(actual);
        }
        
        [Fact]
        function DoesNotChangeArrayIfNoMatchFound(){
            var expected=[1,2,3];
            var actual=expected.slice(0);
            
            Array.Remove(actual,4);
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ReturnsRemovedElement(){
            var expected=3;
            
            var actual=Array.Remove([1,2,expected],expected);
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function CallsPredicateIfPassed(){
            var expected="predicate";
            var actual=null;
            
            Array.Remove([1],1,function(){
                actual=expected;
            });
            
            Assert.Equal(expected,actual);
        }
    }
    
    [Fixture]
    function Shuffle(){
        [Fact]
        function ThrowsIfArrayOmitted(){
            var expected="Array.Shuffle: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Shuffle();
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfPassedInvalidArray(){
            var expected="Array.Shuffle: 'array' must be a valid Array.";
            
            var actual=Record.Exception(function(){
                Array.Shuffle(new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ReturnsAnArray(){
            var expected=[1,2,3,4,5,6,7,8,9,10];
        
            var actual=Array.Shuffle(expected);
            
            Assert.Type(Array,actual);
        }

        [Fact]
        function ReturnsArrayOfSameLength(){
            var expected=[1,2,3,4,5,6,7,8,9,10];
        
            var actual=Array.Shuffle(expected);
            
            Assert.Equal(expected.length,actual.length);
        }
        
        [Fact]
        function ReturnsShuffledArray(){
            var expected=[1,2,3,4,5,6,7,8,9,10];
        
            var actual=Array.Shuffle(expected);
            
            Assert.NotEqual(expected,actual);
        }
        
        [Fact,Data(1,2,3,4,5,6,7,8,9,10)]
        function AllElementsFromSourceAreMovedToTarget(data){
            var expected=[1,2,3,4,5,6,7,8,9,10];
        
            var actual=Array.Shuffle(expected);

            Assert.Contains(data,actual);
        }
    }
}